#include "zen_game_texture.h"
#include "zen_file.h"
#include "zen_number.h"
#include "zen_utils.h"
#include "zen_exception.h"
#include "zen_gles3.h"
#include "zen_image_stb.h"
#include "zen_game_activity.h"
#include <array>

namespace Zen
{
	namespace Game
	{
		TextureCache::TextureCache(std::shared_ptr<Zen::Graphics::Engine> engine)
		{
			mEngine = engine;
			mDelegate = std::make_shared<DefaultTextureLoader>();
		}
		void TextureCache::SetDelegate(std::shared_ptr<TextureLoaderI> delegate)
		{
			mDelegate = delegate;
		}
		std::shared_ptr<Zen::Graphics::Texture> TextureCache::AddImage(std::string const& key, std::shared_ptr<Image> const& image)
		{
			if (!image) return nullptr;
			if (image->Width() == 0 || image->Height() == 0) return nullptr;
			auto p = image->PixelType();
			if (p != EPixel::Grey && p != EPixel::RGB && p != EPixel::RGBA) return nullptr;

			auto texture = mEngine->CreateTexture(image->PixelType(), image->Width(), image->Height(), image->Data());
			if (texture)
			{
				Insert(key, texture);
			}
			return texture;
		}
		std::shared_ptr<Zen::Graphics::Texture> TextureCache::LoadData(std::string const& key)
		{
			if (!mDelegate || !mEngine) return nullptr;
			auto image = mDelegate->OnLoadTexture(key);
			if (!image) return nullptr;
			if (image->Width() == 0 || image->Height() == 0) return nullptr;
			auto p = image->PixelType();
			if (p != EPixel::Grey && p != EPixel::RGB && p != EPixel::RGBA) return nullptr;

			return mEngine->CreateTexture(image->PixelType(), image->Width(), image->Height(), image->Data());
		}
		std::shared_ptr<Image> DefaultTextureLoader::OnLoadTexture(std::string const& key)
		{
			auto data = Zen::Utils::Only()->LoadResource(key);
			if (data.empty()) return nullptr;
			Zen::ImageSTBDecoder decoder;
			return decoder.decode(data);
		}

		void TextureFrame::SetTexture(std::shared_ptr<Zen::Graphics::Texture> texture)
		{
			if (mTexture != texture)
			{
				mTexture = texture;
				SetDirty();
			}
		}
		std::shared_ptr<Zen::Graphics::Texture> TextureFrame::GetTexture()
		{
			return mTexture;
		}
		Point2 TextureFrame::ProjectCoord(Point2 point)
		{
			return point;
		}
		std::vector<Point2> TextureFrame::ProjectCoords(std::vector<Point2> points)
		{
			return std::move(points);
		}

		inline static std::shared_ptr<Zen::Graphics::Buffer> CreateTextureDefaultBuffer()
		{
			auto buffer = Activity::Only()->GetGraphicsEngine()->GenerateBuffer();
			std::vector<Point2> coords = { { 0, 1 }, { 1, 1 }, { 0, 0 }, { 1, 0 } };
			buffer->Initialize(sizeof(Point2) * coords.size(), coords.data());
			return buffer;
		}

		std::shared_ptr<Zen::Graphics::Buffer> TextureFrame::GetDefaultCoordBuffer()
		{
			static auto sBuffer = CreateTextureDefaultBuffer();
			if (mTexture && !mDefaultCoordBuffer)
			{
				mDefaultCoordBuffer = sBuffer;
			}
			return mDefaultCoordBuffer;
		}
		std::shared_ptr<Zen::Graphics::Buffer> TextureFrameInRect::GetDefaultCoordBuffer()
		{
			if (mTexture && mBufferVerion != GetVersion())
			{
				auto buffer = Activity::Only()->GetGraphicsEngine()->GenerateBuffer();
				auto coords = ProjectCoords({ { 0, 1 }, { 1, 1 }, { 0, 0 }, { 1, 0 } });
				buffer->Initialize(sizeof(Point2) * coords.size(), coords.data());
				mDefaultCoordBuffer = buffer;
				mBufferVerion = GetVersion();
			}
			return mDefaultCoordBuffer;
		}
		void TextureFrameInRect::SetTexture(std::shared_ptr<Zen::Graphics::Texture> texture, TextureRect rect, bool flipX, bool flipY)
		{
			if (mTexture != texture || mTextureRect != rect || mFlipX != flipX || mFlipY != flipY)
			{
				mTexture = texture;
				mTextureRect = rect;
				mFlipX = flipX;
				mFlipY = flipY;
				SetDirty();
			}
		}
		Point2 TextureFrameInRect::ProjectCoord(Point2 point)
		{
			float x = (mFlipX ? 1.f - point.x : point.x);
			float y = (mFlipY ? 1.f - point.y : point.y);
			x = (mTextureRect.x1 - mTextureRect.x0) * x + mTextureRect.x0;
			y = (mTextureRect.y1 - mTextureRect.y0) * y + mTextureRect.y0;
			return { x, y };
		}
		std::vector<Point2> TextureFrameInRect::ProjectCoords(std::vector<Point2> points)
		{
			for (auto& p : points) p = ProjectCoord(p);
			return std::move(points);
		}
		TextureRect const& TextureFrameInRect::GetTextureRect() const
		{
			return mTextureRect;
		}
		void TextureFrameInRect::SetFlipX(bool v)
		{
			if (mFlipX != v)
			{
				mFlipX = v;
				SetDirty();
			}
		}
		void TextureFrameInRect::SetFlipY(bool v)
		{
			if (mFlipY != v)
			{
				mFlipY = v;
				SetDirty();
			}
		}
		void TextureFrameInRect::SetFlip(bool x, bool y)
		{
			if (mFlipX != x || mFlipY != y)
			{
				mFlipX = x;
				mFlipY = y;
				SetDirty();
			}
		}
		bool TextureFrameInRect::IsFlipX() const
		{
			return mFlipX;
		}
		bool TextureFrameInRect::IsFlipY() const
		{
			return mFlipY;
		}
		int TextureFrameI::GetVersion()
		{
			return mVersion;
		}
		void TextureFrameI::SetDirty()
		{
			++mVersion;
		}
	} // namespace Game
} // namespace Zen

/**
 default system texture
 */
namespace Zen
{
	namespace Game
	{

		BuiltinImages* BuiltinImages::Only()
		{
			static auto me = new BuiltinImages;
			return me;
		}

		std::shared_ptr<Zen::Image> BuiltinImages::CreateWithAlphaChannel(EPixel format, size_t w_h, void const* alphas)
		{
			if (format == EPixel::Grey)
			{
				return Image::GenerateWithData(format, w_h, w_h, alphas);
			}
			auto image = Image::GenerateBlank(format, w_h, w_h, 0xff);
			if (format == EPixel::RGBA)
			{
				image->FillAlphaFrom(Zen::EPixel::Grey, alphas);
			}
			else if (format == EPixel::RGB)
			{
				image->FillColorFrom(Zen::EPixel::Grey, alphas);
			}
			return image;
		}

		std::shared_ptr<Zen::Image> BuiltinImages::CreateWhiteTexture(size_t w_h)
		{
			if (w_h == 0) return nullptr;
			return Zen::Image::GenerateBlank(Zen::EPixel::RGBA, w_h, w_h, 0xff);
		}
		std::shared_ptr<Zen::Image> BuiltinImages::CreateBlackTexture(size_t w_h)
		{
			if (w_h == 0) return nullptr;
			return Zen::Image::GenerateBlank(Zen::EPixel::RGBA, w_h, w_h, 0);
		}

		std::shared_ptr<Zen::Image> BuiltinImages::CreateParticleTexture(EPixel format, size_t w_h)
		{
			float r = (float)w_h / 2;

			std::vector<uint8_t> buf(w_h * w_h);
			auto value = buf.data();
			for (int i = 0; i < (int)w_h; ++i)
			{
				float x2 = Zen::Number::Square(r - i); //signed
				for (int j = 0; j < (int)w_h; ++j)
				{
					float y2 = Zen::Number::Square(r - j);
					float v = (float)(sqrtf(x2 + y2) / r);
					if (v > 0.99)
						value[0] = 0;
					else
						value[0] = (uint8_t)(255.f * (1 - v));
					++value;
				}
			}
			return CreateWithAlphaChannel(format, w_h, buf.data());
		}
		std::shared_ptr<Zen::Image> BuiltinImages::CreateCrossTexture(EPixel format, size_t w_h)
		{
			int r = int(w_h / 2);

			std::vector<uint8_t> buf(w_h * w_h);
			auto value = buf.data();
			for (int i = 0; i < (int)w_h; ++i)
			{
				int x = std::abs(r - i);
				for (int j = 0; j < (int)w_h; ++j)
				{
					int y = std::abs(r - j);
					if (std::abs(x - y) > 8)
						*value = 0;
					else
						*value = 255;
					++value;
				}
			}
			return CreateWithAlphaChannel(format, w_h, buf.data());
		}

		std::shared_ptr<Zen::Image> BuiltinImages::CreateCircleTexture(EPixel format, size_t w_h)
		{
			float r = (float)w_h / 2;

			std::vector<uint8_t> buf(w_h * w_h);
			auto value = buf.data();
			for (int i = 0; i < (int)w_h; ++i)
			{
				float x2 = Zen::Number::Square(r - i);
				for (int j = 0; j < (int)w_h; ++j)
				{
					float y2 = Zen::Number::Square(r - j);
					float v = (float)(sqrtf(x2 + y2) / r);
					if (v >= 1.f)
						*value = 0;
					else if (v > 0.9f)
						*value = (uint8_t)(254.f * Zen::Number::Square(10.f * (1.f - v)));
					else if (v > 0.8f)
						*value = (uint8_t)(254.f * Zen::Number::Square(10.f * (v - .8f)));
					else
						*value = 0;
					++value;
				}
			}
			return CreateWithAlphaChannel(format, w_h, buf.data());
		}
	} // namespace Game
} // namespace Zen
